package cn.zyq.demo.system.pay.core.client.impl.alipay;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.math.MathUtil;
import cn.zyq.demo.system.pay.core.client.AbstractPayCodeMapping;
import cn.zyq.demo.system.pay.core.client.PayCommonResult;
import cn.zyq.demo.system.pay.core.client.dto.*;
import cn.zyq.demo.system.pay.core.client.impl.AbstractPayClient;
import cn.zyq.demo.system.pay.core.enums.PayNotifyRefundStatusEnum;
import cn.zyq.demo.system.utils.DateUtils;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayFundTransUniConsultModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.Participant;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayFundTransUniTransferRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


/**
 * 支付宝抽象类， 实现支付宝统一的接口。如退款
 *
 * @author jason
 */
@Slf4j
public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayClientConfig> {

    protected DefaultAlipayClient client;

    public AbstractAlipayClient(Integer channelId, String channelCode,
                                AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
        super(channelId, channelCode, config, codeMapping);
    }

    @Override
    @SneakyThrows
    protected void doInit() {
        AlipayConfig alipayConfig = new AlipayConfig();
        BeanUtil.copyProperties(config, alipayConfig, false);
        this.client = new DefaultAlipayClient(alipayConfig);
    }

    /**
     * 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
     * //https://opendocs.alipay.com/open/203/105286
     *
     * @param data 通知结果
     * @return 解析结果 PayOrderNotifyRespDTO
     * @throws Exception 解析失败，抛出异常
     */
    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
        Map<String, String> params = strToMap(data.getBody());

        return PayOrderNotifyRespDTO.builder()
                .outTradeNo(params.get("out_trade_no"))
                .channelOrderNo(params.get("trade_no"))
                .channelUserId(params.get("seller_id"))
                .tradeStatus(params.get("trade_status"))
                .totalAmount(yuanStr2fen(params.get("total_amount")))
                .payerTotalAmount(yuanStr2fen(params.get("buyer_pay_amount")))
                .successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
                .data(data.getBody()).build();
    }

    @Override
    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
        Map<String, String> params = strToMap(notifyData.getBody());
        PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(
                params.get("trade_no"))
                .tradeNo(params.get("out_trade_no"))
                .reqNo(params.get("out_biz_no"))
                .refundFee(params.get("refund_fee"))
                .status(PayNotifyRefundStatusEnum.SUCCESS)
                .refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
                .build();
        return notifyDTO;
    }

    @Override
    public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
        if (notifyData.getParams().containsKey("refund_fee")) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
        boolean verifyResult = false;
        try {
            verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2");
        } catch (AlipayApiException e) {
            log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", JSON.toJSONString(notifyData.getParams()), e);
        }
        return verifyResult;
    }

    /**
     * 支付宝统一的退款接口 alipay.trade.refund
     *
     * @param reqDTO 退款请求 request DTO
     * @return 退款请求 Response
     */
    @Override
    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        model.setTradeNo(reqDTO.getChannelOrderNo());
        model.setOutTradeNo(reqDTO.getPayTradeNo());
        model.setOutRequestNo(reqDTO.getMerchantRefundId());
        model.setRefundAmount(reqDTO.getAmount().toString());
        model.setRefundReason(reqDTO.getReason());
        AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
        refundRequest.setBizModel(model);
        try {
            AlipayTradeRefundResponse response = client.certificateExecute(refundRequest);
            log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", JSON.toJSONString(response));
            if (response.isSuccess()) {
                //退款导致触发的异步通知是发送到支付接口中设置的notify_url
                //支付宝不返回退款单号，设置为空
                PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
                respDTO.setChannelRefundId(response.getTradeNo());
                return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
            }
            // 失败。需要抛出异常
            return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
        } catch (AlipayApiException e) {
            // TODO 记录异常日志
            log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时，退款状态未知]", JSON.toJSONString(reqDTO), e);
            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
        }
    }

    @Override
    protected PayCommonResult<PayOrderTransferRespDTO> doUnifiedTransfer(PayOrderTransferReqDTO reqDTO) throws Throwable {
        AlipayFundTransUniConsultModel model = new AlipayFundTransUniConsultModel();
        model.setOutBizNo(reqDTO.getOutTradeNo());
        model.setTransAmount(String.valueOf(MathUtil.centToYuan(reqDTO.getAmount())));
        model.setProductCode("TRANS_ACCOUNT_NO_PWD");
        model.setOrderTitle(reqDTO.getTitle());
        model.setRemark(reqDTO.getRemark());
        model.setBizScene("DIRECT_TRANSFER");
        Participant payeeInfo = new Participant();
        payeeInfo.setIdentity(reqDTO.getUserAccount());
        payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
        payeeInfo.setName(reqDTO.getUname());
        model.setPayeeInfo(payeeInfo);
        AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
        request.setBizModel(model);

        try {
//            String privateKey = PayAliProperties.privateKey;
//            CertAlipayRequest alipayConfig = new CertAlipayRequest();
//            alipayConfig.setPrivateKey(privateKey);
//            alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
//            alipayConfig.setAppId("2021003199627948");
//            alipayConfig.setCharset("UTF8");
//            alipayConfig.setSignType("RSA2");
//            alipayConfig.setEncryptor("");
//            alipayConfig.setFormat("json");
//            alipayConfig.setCertPath(PayAliProperties.appCertContent);
//            alipayConfig.setAlipayPublicCertPath(PayAliProperties.alipayPublicCertContent);
//            alipayConfig.setRootCertPath(PayAliProperties.rootCertContent);
//            AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);

            AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
            log.info("[doUnifiedRefund][response({}) 发起转账提现 渠道返回", JSON.toJSONString(response));
            if (response.isSuccess()) {
                //退款导致触发的异步通知是发送到支付接口中设置的notify_url
                //支付宝不返回退款单号，设置为空
                PayOrderTransferRespDTO respDTO = new PayOrderTransferRespDTO();
                respDTO.setChannelOrderNo(response.getOrderId());
                respDTO.setSuccessTime(DateUtils.parseDate(response.getTransDate()));
                respDTO.setTradeStatus(response.getStatus());
                respDTO.setChannelFundOrderId(response.getPayFundOrderId());
                respDTO.setData(JSON.toJSONString(response));
                respDTO.setOutTradeNo(reqDTO.getOutTradeNo());
                return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
            }
            // 失败。需要抛出异常
            return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
        } catch (AlipayApiException e) {
            // TODO 记录异常日志
            log.error("[doUnifiedRefund][request({}) 发起转账提现,网络读超时，退款状态未知]", JSON.toJSONString(reqDTO), e);
            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
        }
    }


    /**
     * 支付宝统一回调参数  str 转 map
     *
     * @param s 支付宝支付通知回调参数
     * @return map 支付宝集合
     */
    public static Map<String, String> strToMap(String s) {
        // TODO @zxy：这个可以使用 hutool 的 HttpUtil decodeParams 方法么？
        Map<String, String> stringStringMap = new HashMap<>();
        // 调整时间格式
        String s3 = s.replaceAll("%3A", ":");
        // 获取 map
        String s4 = s3.replace("+", " ");
        String[] split = s4.split("&");
        for (String s1 : split) {
            String[] split1 = s1.split("=");
            stringStringMap.put(split1[0], split1[1]);
        }
        return stringStringMap;
    }

}
